﻿using System;
using System.Collections.Generic;
using System.Reflection;
using Alchemy.Classes;
using Eventd.Socket;
using Shot.Presentation.Foundation;
using Shot.Routing;

namespace Shot
{
    /// <summary>
    /// An App is the core foundation of a Shot application. It translates incoming Web Socket requests into AppInstances,
    /// then routes them accordingly based on the configured Router.
    /// </summary>
    public class App : EventdServer
    {
        /// <summary>
        /// Event triggered when a route is requested
        /// </summary>
        public static string RouteEvent
        {
            get
            {
                return "hashchange";
            }
        }

        /// <summary>
        /// Handshake hello event
        /// </summary>
        public static string HelloEvent
        {
            get
            {
                return "hello";
            }
        }

        /// <summary>
        /// App's associated router
        /// </summary>
        public IRouter Router { get; set; }

        /// <summary>
        /// Router path string to identify the router class to use.
        /// </summary>
        public string RouterPath { get; set; }

        /// <summary>
        /// Presentation manager
        /// </summary>
        public IPresentationManager PresentationManager { get; set; }

        /// <summary>
        /// Session-based data storage on the server level
        /// </summary>
        public Dictionary<string, object> Data { get; set; } 

        /// <summary>
        /// ConstructorC:\Users\Jesse\Documents\eventd-cs\Shot\Core\Router\
        /// </summary>
        public App()
        {
            RouterPath = "";
            Data = new Dictionary<string, object>();
            PresentationManager = InstantiatePresentationManager();
            Started += OnStarted;
        }

        /// <summary>
        /// Get an AppInstance for a UserContext
        /// </summary>(
        /// <param name="userContext"></param>
        /// <returns></returns>
        public override IEventdClient GetEventdClientFromSocket(UserContext userContext)
        {
            return new AppInstance
            {
                Context = userContext,
                Server = this,
                App = this
            };
        }

        /// <summary>
        /// Start the app. Automatically triggers the KeepAlive method
        /// </summary>
        public override void Start()
        {
            base.Start();
            Router = InstantiateNewRouterFromConfiguration();
            KeepAlive();
        }

        /// <summary>
        /// Present a presenter (Corresponds to PresentationManager present)
        /// </summary>
        /// <param name="presenter"></param>
        /// <param name="client"></param>
        public void Present(IPresenter presenter, AppInstance client)
        {
            PresentationManager.PresentFor(presenter, client);
        }

        private void OnStarted(object sender, EventArgs eventArgs)
        {
            Console.WriteLine("App listening at ws://{0}:{1}", Host, Port);
            HandleIncomingClientConnections();
        }

        private void HandleIncomingClientConnections()
        {
            On(ConnectionEmission, eventdClient => HandleIncomingClient((AppInstance)eventdClient));
        }

        private void HandleIncomingClient(AppInstance appInstance)
        {
            appInstance.On(HelloEvent, sessionKey => HandshakeWithClient(appInstance));

            appInstance.On(RouteEvent, routeName =>
            {
                appInstance.After();
                appInstance.Ready += x => Router.Route(routeName, appInstance);
            });
        }

        private void HandshakeWithClient(AppInstance client)
        {
            client.After();
            client.Ready += sender => client.Emit("hello");
        }

        private IRouter InstantiateNewRouterFromConfiguration()
        {
            var routerConfigSetting = RouterPath;

            var assemblyName = routerConfigSetting.Split(';')[0];
            var typeName = routerConfigSetting.Split(';')[1];

            var routerAssembly = Assembly.Load(assemblyName);
            var routerType = routerAssembly.GetType(typeName);

            var routerInstance = (IRouter)Activator.CreateInstance(routerType);
            routerInstance.App = this;

            return routerInstance;
        }

        private PresentationManager InstantiatePresentationManager()
        {
            return new PresentationManager
            {
                App = this
            };
        }
    }
}
